//  Copyright (c) Microsoft Corporation.  All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Globalization;
using System.IdentityModel.Selectors;

using System.IdentityModel.Claims;
using System.IdentityModel.Policy;

namespace EAI.SAML
{
    public class EAISamlAuthnStatement : EAISamlStatement
    {
        DateTime authInstance = DateTime.UtcNow;
        string sessionIndex;
        DateTime sessionNotOnOrAfter = DateTime.MaxValue.ToUniversalTime();

        // SubjectLocality members. Flattend out into members.
        string address;
        string dnsName;

        // AuthnContext members. Flattened out here.
        Uri authnContextClassRef;
        Uri authnContextDeclRef;
        readonly ImmutableCollection<Uri> authenticatingAuthority = new ImmutableCollection<Uri>();
        
        // AuthnContextDecl is declared as anyType. This can be added by extending this class 
        // and providing a custom serializers.
        
        bool isReadOnly = false;

        public EAISamlAuthnStatement()
        {
        }

        public EAISamlAuthnStatement(XmlReader reader, EAISamlSerializer serializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            this.ReadXml(reader, serializer, keyInfoSerializer, outOfBandTokenResolver);
        }

        public EAISamlAuthnStatement(DateTime authInstance, Uri authnContextClassRef, Uri authnContextDeclRef, IEnumerable<Uri> authenticatingAuthority)
        {
            if ((authnContextClassRef == null) && (authnContextDeclRef == null))
                throw new ArgumentException("AuthnStatement requires at least on authnContextClassRef or a authnContextDeclRef.");

            this.authInstance = authInstance;
            this.authnContextClassRef = authnContextClassRef;
            this.authnContextDeclRef = authnContextDeclRef;
            foreach (Uri authorityUri in authenticatingAuthority)
            {
                this.authenticatingAuthority.Add(authorityUri);
            }
        }

        public DateTime AuthInstance
        {
            get
            {
                return this.authInstance;
            }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only");

                this.authInstance = value;
            }
        }

        public string SessionIndex
        {
            get
            {
                return this.sessionIndex;
            }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                this.sessionIndex = value;
            }
        }

        public DateTime SessionNotOnOrAfter
        {
            get
            {
                return this.sessionNotOnOrAfter;
            }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                this.sessionNotOnOrAfter = value;
            }
        }

        public string Address
        {
            get
            {
                return this.address;
            }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                if (value == null)
                    throw new ArgumentNullException("value");
                
                this.address = value;
            }
        }

        public string DNSName
        {
            get
            {
                return this.dnsName;
            }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                if (value == null)
                    throw new ArgumentNullException("value");

                this.dnsName = value;
            }
        }

        public Uri AuthnContextClassRef
        {
            get
            {
                return this.authnContextClassRef;
            }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                if (value == null)
                    throw new ArgumentNullException("value");

                this.authnContextClassRef = value;
            }
        }

        public Uri AuthnContextDeclRef
        {
            get
            {
                return this.authnContextDeclRef;
            }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                if (value == null)
                    throw new ArgumentNullException("value");

                this.authnContextDeclRef = value;
            }
        }

        public IList<Uri> AuthenticatingAuthority
        {
            get
            {
                return this.authenticatingAuthority;
            }
        }

        void CheckObjectValidity()
        {
            if (this.authnContextDeclRef == null)
                throw new InvalidOperationException("saml:AuthnStatement should have an authnContextDeclRef specified.");
        }

        public override void MakeReadOnly()
        {
            if (!this.isReadOnly)
            {
                this.authenticatingAuthority.MakeReadOnly();
                this.isReadOnly = true;
            }
        }

        public override IAuthorizationPolicy CreatePolicy(ClaimSet issuer, EAISamlAssertion assertion, EAISamlSecurityTokenAuthenticator tokenAuthenticator)
        {
            // This sample does not implement this method. Take a look at 
            // EAISamlAttributeStatement.CreatePolicy, for an example of how this
            // method can be implemented.
            throw new Exception("The method or operation is not implemented.");
        }

        public override void ReadXml(XmlReader reader, EAISamlSerializer serializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            string attributeValue = reader.GetAttribute(EAISamlConstants.AuthnInstanceAttribute);
            if (String.IsNullOrEmpty(attributeValue))
                throw new SecurityTokenException("<saml:AuthnStatement> is missing the required AuthnInstance attribute.");

            reader.Read();

            this.authInstance = Convert.ToDateTime(attributeValue).ToUniversalTime();

            this.sessionIndex = reader.GetAttribute(EAISamlConstants.SessionIndexAttribute);

            attributeValue = reader.GetAttribute(EAISamlConstants.SessionNotOnOrAfterAttribute);
            if (!String.IsNullOrEmpty(attributeValue))
                this.sessionNotOnOrAfter = Convert.ToDateTime(attributeValue).ToUniversalTime();

            if (reader.IsStartElement(EAISamlConstants.SubjectLocality, EAISamlConstants.Namespace))
            {
                this.address = reader.GetAttribute(EAISamlConstants.AddressAttribute);
                this.dnsName = reader.GetAttribute(EAISamlConstants.DNSNameAttribute);
                if (reader.IsEmptyElement)
                {
                    reader.MoveToContent();
                    reader.Read();
                }
                else
                {
                    reader.ReadEndElement();
                }
            }

            reader.ReadStartElement(EAISamlConstants.AuthnContext, EAISamlConstants.Namespace);
            if (reader.IsStartElement(EAISamlConstants.AuthnContextClassRef, EAISamlConstants.Namespace))
            {
                string valueString = reader.ReadString();
                if (String.IsNullOrEmpty(valueString))
                    throw new SecurityTokenException("<saml:AuthnContext> cannot have null or empty string as the AuthnContextClassRef element value.");
                this.authnContextClassRef = new Uri(valueString);
                reader.ReadEndElement();
            }

            if (reader.IsStartElement(EAISamlConstants.AuthnContextDeclRef, EAISamlConstants.Namespace))
            {
                string valueString = reader.ReadString();
                if (String.IsNullOrEmpty(valueString))
                    throw new SecurityTokenException("<saml:AuthnContext> cannot have null or empty string as the AuthnContextDeclRef element.");
                this.authnContextDeclRef = new Uri(valueString);
                reader.ReadEndElement();
            }

            if ((this.authnContextClassRef == null) || (this.authnContextDeclRef == null))
                throw new SecurityTokenException("<saml:AuthnContext> should have <saml:AuthnContextClassRef> or <saml:AuthnContextDeclRef>,");

            while (reader.IsStartElement(EAISamlConstants.AuthenticatingAuthority, EAISamlConstants.Namespace))
            {
                this.authenticatingAuthority.Add(new Uri(reader.ReadString()));
                reader.ReadEndElement();
            }

            reader.ReadEndElement();

            reader.ReadEndElement();
        }

        public override void WriteXml(XmlWriter writer, SecurityTokenSerializer serializer)
        {
            CheckObjectValidity();

            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(EAISamlConstants.AuthnStatement, EAISamlConstants.Namespace);

            writer.WriteAttributeString(EAISamlConstants.AuthnInstanceAttribute, this.authInstance.ToString(EAISamlConstants.GeneratedDateTimeFormat, CultureInfo.InvariantCulture));

            if (this.sessionIndex != null)
                writer.WriteAttributeString(EAISamlConstants.SessionIndexAttribute, this.sessionIndex);

            if (this.sessionNotOnOrAfter != null)
                writer.WriteAttributeString(EAISamlConstants.SessionNotOnOrAfterAttribute, this.sessionNotOnOrAfter.ToString(EAISamlConstants.GeneratedDateTimeFormat, CultureInfo.InvariantCulture));

            if ((this.address != null) || (this.dnsName != null))
            {
                writer.WriteStartElement(EAISamlConstants.SubjectLocality, EAISamlConstants.Namespace);
                if (this.address != null)
                    writer.WriteAttributeString(EAISamlConstants.AddressAttribute, this.address);

                if (this.dnsName != null)
                    writer.WriteAttributeString(EAISamlConstants.DNSNameAttribute, this.dnsName);

                writer.WriteEndElement();
            }

            writer.WriteStartElement(EAISamlConstants.AuthnContext, EAISamlConstants.Namespace);
            if (this.authnContextClassRef != null)
            {
                writer.WriteStartElement(EAISamlConstants.AuthnContextClassRef, EAISamlConstants.Namespace);
                writer.WriteString(this.authnContextClassRef.AbsoluteUri);
                writer.WriteEndElement();
            }

            if (this.authnContextDeclRef != null)
            {
                writer.WriteStartElement(EAISamlConstants.AuthnContextDeclRef, EAISamlConstants.Namespace);
                writer.WriteString(this.authnContextDeclRef.AbsoluteUri);
                writer.WriteEndElement();
            }

            if (this.authenticatingAuthority.Count > 0)
            {
                for (int i = 0; i < this.authenticatingAuthority.Count; ++i)
                {
                    writer.WriteStartElement(EAISamlConstants.AuthenticatingAuthority, EAISamlConstants.Namespace);
                    writer.WriteString(this.authenticatingAuthority[i].AbsoluteUri);
                    writer.WriteEndElement();
                }
            }

            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }

}
